/** Represents a buffer for screen output.
@param width Width in cells.
@param height Height in cells.
*/
function ScreenBuffer(width, height)
{
	this.width = width;
	this.height = height;
	this._cells = [];
	for(var i = 0; i < width * height; ++i)
	{
		this._cells.push(0);
	}
	this._stringFgColor = Color.white;
	this._stringBgColor = Color.black;
}
ScreenBuffer.prototype =
{
	/** Sets attributes about a cell. All parameters are optional but must be
	replaced by null.
	@param x X position.
	@param y Y position.
	@param c Character.
	@param fg Foreground color.
	@param bg Background color.
	*/
	setCell: function(x, y, c, fg, bg)
	{
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return;
		var cell = this._cells[y * this.width + x];
		if(typeof(c) == "string")
			c = c.charCodeAt(0);
		if(c !== null)
			c &= 0xff;
		if(c !== null)
			cell = (cell & 0xff00) | c;
		if(fg !== null)
			cell = (cell & 0xf0ff) | (fg << 8);
		if(bg !== null)
			cell = (cell & 0x0fff) | (bg << 12);
		this._cells[y * this.width + x] = cell;
	},
	/** Sets the foreground color.
	 *@param c The color to use.
	 */
	fg: function(c)
	{
		this._stringFgColor = c;
	},
	/** Sets the background color.
	 *@param c The color to use.
	 */
	bg: function(c)
	{
		this._stringBgColor = c;
	},
	/** Gets the raw cell at the specified location, or null if out of range.
	 *
	 * The raw cell returned should only be used as an argument to setRawCell.
	 *@param x X position;
	 *@param y Y position;
	 */
	getCell: function(x, y)
	{
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return null;
		return this._cells[y * this.width + x];
	},
	/** Sets a cell from raw cell data.
	 *@param x X location.
	 *@param y Y location.
	 *@param cell The cell value as returned from getCell.
	 */
	setRawCell: function(x, y, cell)
	{
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return;
		this.setCell(x, y, cell & 0x00ff, (cell & 0x0f00) >> 8, (cell & 0xf000) >> 12);
	},
	/** Draws a string of ASCII text to the buffer, ignoring any character
	outside the range 32-127.
	

	Special text color escapements are supported in the form of "%XX", where X
	is a hex digit of one of the standard ASCII color values (see
	Color.asciiCodes). The first X sets the foreground color, the second the
	background color. Use "%%" for a percent sign.

	Special characters are supported in the form of "#XX", where XX is a hex
	number specifiying the character (from Code Page 437) to print. Use "##" for
	a hash sign.
	
	@param x X location.
	@param y Y location.
	@param str The string to blit.
	@param maxWidth The maximum width of the string to actually blit (optional). If specified this will clear to the end of the width.
	@param bg The background color to fill with (optional, defaults to the current text background color).
	*/
	blitString: function(x, y, str, maxWidth, bg)
	{
		var cx = 0, i, state = 0, c, code, temp;
		
		for(i = 0; i < str.length; ++i)
		{
			c = str.charAt(i);
			code = c.charCodeAt(0);
			if(code < 32 ||
				code > 127)
				continue;
			switch(state)
			{
				case 0:
					if(c == "%")
						state = 1;
					else if(c == "#")
						state = 3;
					else
					{
						if(maxWidth === undefined ||
							cx < maxWidth)
							this.setCell(x + cx, y, c, this._stringFgColor, this._stringBgColor);
						cx++;
					}
					break;
				case 1:
					if(c == "%")
					{
						if(maxWidth === undefined ||
							cx < maxWidth)
							this.setCell(x + cx, y, c, this._stringFgColor, this._stringBgColor);
						cx++;
						state = 0;
						break;
					}
					temp = Color.asciiCodes[c];
					if(temp !== undefined)
						this._stringFgColor = temp;
					state = 2;
					break;
				case 2:
					temp = Color.asciiCodes[c];
					if(temp !== undefined)
						this._stringBgColor = temp;
					state = 0;
					break;
				case 3:
					if(c == "#")
					{
						if(maxWidth === undefined ||
							cx < maxWidth)
							this.setCell(x + cx, y, c, this._stringFgColor, this._stringBgColor);
						cx++;
						state = 0;
						break;
					}
					temp = c;
					state = 4;
					break;
				case 4:
					c = parseInt("0x" + temp + c);
					if(maxWidth === undefined ||
						cx < maxWidth)
						this.setCell(x + cx, y, c, this._stringFgColor, this._stringBgColor);
					cx++;
					state = 0;
					break;
				}
		}
		if(maxWidth !== undefined)
		{
			if(bg === undefined)
				bg = this._stringBgColor;
			for(; cx < maxWidth; ++cx)
			{
				this.setCell(x + cx, y, " ", null, bg);
			}
		}
	},
	/** Utility function for calling blitString with a maximum width and
	 * centering the text within this width.
	 *@param x X location.
	 *@param y Y location.
	 *@param str The string to blit.
	 *@param maxWidth The maximum width of the string to actually blit. The text will be centered within this width.
	 *@param bg The background fill color (optional, defaults to black).
	 */
	blitStringCentered: function(x, y, str, maxWidth, bg)
	{
		if(bg === undefined)
			bg = Color.black;
		
		// Early short-circut
		if(str.length >= maxWidth)
		{
			this.blitString(x, y, str, maxWidth, bg);
			return;
		}
		
		var left = Math.floor((maxWidth - util.getFormattedStringLength(str)) / 2);
		this.blitRect(x, y, left, 1, " ", null, bg);
		this.blitString(x + left, y, str, maxWidth - left, bg);
	},
	/** Draws a rectangle with the specified attributes.
	@param x X position.
	@param y Y position.
	@param width Width.
	@param height Height.
	@param c Character.
	@param fg Foreground color.
	@param bg Background color.
	*/
	blitRect: function(x, y, width, height, c, fg, bg)
	{
		for(iy = y; iy < y + height; ++iy)
		{
			for(ix = x; ix < x + width; ++ix)
			{
				this.setCell(ix, iy, c, fg, bg);
			}
		}
	},
	/** Draws a progress bar by changing background colors.
	 *@param x X position of the bar.
	 *@param y Y position of the bar.
	 *@param width Width of the bar.
	 *@param percent Percent the bar should be filled to.
	 *@param full Color of a full cell.
	 *@param half Color of a half cell.
	 *@param empty Color of an empty cell.
	 */
	blitProgressBar: function(x, y, width, percent, full, half, empty)
	{
		var rem, fullWidth = 0, halfWidth = 0, emptyWidth = 0;
		if(percent > 0)
		{
			fullWidth = Math.floor(width * percent);
			if(fullWidth >= width)
			{
				fullWidth = width;
			}
			else
			{
				rem = width * percent - fullWidth;
				if(rem > 0.5)
				{
					fullWidth++
					if(fullWidth >= width)
					{
						fullWidth = width;
					}
				}
				else
				{
					halfWidth = 1;
				}

				emptyWidth = width - (fullWidth + halfWidth);
				if(emptyWidth < 0)
					emptyWidth = 0;
			}
		}
		else
		{
			emptyWidth = width;
		}
		if(fullWidth > 0)
			this.blitRect(x, y, fullWidth, 1, null, null, full);
		if(halfWidth > 0)
			this.blitRect(x + fullWidth, y, halfWidth, 1, null, null, half);
		if(emptyWidth > 0)
			this.blitRect(x + fullWidth + halfWidth, y, emptyWidth, 1, null, null, empty);
	},
	/** Draws a box.
	@param x X position of box.
	@param y Y position of box.
	@param width Width of box.
	@param height Height of box.
	@param c Character to draw.
	@param fg Foreground color.
	@param bg Background color.
	*/
	drawBox: function(x, y, width, height, c, fg, bg)
	{
		// Horizontal lines
		this.blitRect(x, y, width, 1, c, fg, bg);
		this.blitRect(x, y + height - 1, width, 1, c, fg, bg);
		
		// Vertical lines
		this.blitRect(x, y + 1, 1, height - 2, c, fg, bg);
		this.blitRect(x + width - 1, y + 1, 1, height - 2, c, fg, bg);
	},
	/** Draws a menu box around )on the outside of) a given area.
	@param x X position of area.
	@param y Y position of area.
	@param width Width of area.
	@param height Height of area.
	@param fg Foreground color (optional, defaults to white)
	@param bg Background color (optional, defaults to black)
	@param ds Drop Shadow color (optional, defaults to darkGray)
	*/
	drawMenuBox: function(x, y, width, height, fg, bg, ds)
	{
		if(fg === undefined)
			fg = Color.black;
		if(bg === undefined)
			bg = Color.white;
		if(ds === undefined)
			ds = Color.darkGray;
		
		// Horizontal lines
		this.blitRect(x - 1, y - 1, width + 2, 1, Char.DBAR_EW, fg, bg);
		this.blitRect(x - 1, y + height, width + 2, 1, Char.DBAR_EW, fg, bg);
		
		// Vertical lines
		this.blitRect(x - 1, y, 1, height, Char.DBAR_NS, fg, bg);
		this.blitRect(x + width, y, 1, height, Char.DBAR_NS, fg, bg);

		// Corners
		this.setCell(x - 1, y - 1, Char.DBAR_ES, null, null);
		this.setCell(x - 1, y + height, Char.DBAR_NE, null, null);
		this.setCell(x + width, y - 1, Char.DBAR_SW, null, null);
		this.setCell(x + width, y + height, Char.DBAR_NW, null, null);
		
		// Drop shadown
		this.blitRect(x, y + height + 1, width + 2, 1, " ", null, ds);
		this.blitRect(x + width + 1, y, 1, height + 1, " ", null, ds);
	},
	/** Draws every cell in the buffer.
	@param c Character.
	@param fg Foreground color.
	@param bg Background color.
	*/
	clear: function(c, fg, bg)
	{
		this.blitRect(0, 0, this.width, this.height, c, fg, bg);
	},
	/** Copies an area of the buffer to another area. These areas may overlap.
	 *@param x X position of area to copy.
	 *@param y Y position of area to copy.
	 *@param width Width of area to copy.
	 *@param height Height of area to copy.
	 *@param dx Destination X position to copy to.
	 *@param dy Destination Y position to copy to.
	 */
	copyRectTo: function(x, y, width, height, dx, dy)
	{
		var ofs = 0;
		var buffer = [];
		var ix, iy, cell;
		for(iy = y; iy < y + height; ++y)
		{
			for(ix = x; ix < x + width; ++x)
			{
				cell = this._cells[iy * this.width + ix];
				buffer.push(new ScreenCell(cell.c, cell.fg, cell.bg));
			}
		}
		for(iy = y; iy < y + height; ++y)
		{
			for(ix = x; ix < x + width; ++x)
			{
				cell = buffer[ofs++];
				this.setCell(ix, iy, cell.c, cell.fg, cell.bg);
			}
		}
	},
	/** Pushes the contents of the buffer to another buffer
	@param dest Destination buffer.
	@param x X offset.
	@param y Y offset.
	*/
	blitTo: function(dest, x, y)
	{
		var ix, iy, ofs = 0, cell;
		
		for(iy = 0; iy < this.height; ++iy)
		{
			for(ix = 0; ix < this.width; ++ix)
			{
				cell = this._cells[ofs++];
				dest.setCell(ix + x, iy + y, cell & 0x00ff, (cell & 0x0f00) >> 8, (cell & 0xf000) >> 12);
			}
		}
	}
};
ron.registerCtor(ScreenBuffer, "ScreenBuffer");
